package qq2564874169.tuaotuao.fx.app.ro;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
import qq2564874169.tuaotuao.fx.*;
import qq2564874169.tuaotuao.fx.app.FxSpringApp;
import qq2564874169.tuaotuao.fx.app.RequestEventArgs;
import qq2564874169.tuaotuao.fx.io.StringReader;
import qq2564874169.tuaotuao.fx.utils.*;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class ROContext implements FxAutoCloseable {
    private static final String bkey = UUID.randomUUID().toString();
    private static final String akey = UUID.randomUUID().toString();
    private static final String ekey = UUID.randomUUID().toString();
    public static final EventDispatcher<ProcessBeforeEventArgs> evBefore = new EventDispatcher<>(bkey);
    public static final EventDispatcher<ProcessAfterEventArgs> evAfter = new EventDispatcher<>(akey);
    public static final EventDispatcher<ProcessErrorEventArgs> evError = new EventDispatcher<>(ekey);
    public static final List<HostProvider> remoteProviders = new ArrayList<>();
    private static final String prefix = "/fxro/";
    private static final Map<String, ROMeta> metaMap = new HashMap<>(8);
    public final Map<String, String> header = new HashMap<>(8);
    private final ApplicationContext context;

    public ROContext(ApplicationContext context) {
        this.context = context;
    }

    @Override
    public void close() {
        header.clear();
    }

    public <T> T exec(T ro) {
        var remoteFirst = false;
        if (ro instanceof RO t) {
            remoteFirst = t.remoteFirst();
        }
        if (remoteFirst && !remoteProviders.isEmpty()) {
            httpExec(ro);
        } else {
            var meta = findMeta(ro.getClass());
            if (meta != null) {
                var handle = context.getBean(meta.handleType);
                var pc = new ProcessContext(ro);
                pc.header.putAll(header);
                exec(handle, pc);
            } else if (!remoteProviders.isEmpty()) {
                httpExec(ro);
            } else {
                throw new RuntimeException("没有找到" + getROName(ro.getClass()) + "的本地handle，也没有配置远程服务。");
            }
        }
        return ro;
    }

    private void httpExec(Object ro) {
        var roname = getROName(ro.getClass());
        var host = StringUtil.trimRight(getRemoteHost(roname, ro.getClass()), "/");
        FxAssert.isNotNull(host, "没有找到远程服务地址：" + roname);
        var list = new ArrayList<ROMsg>();
        for (var k : header.keySet()) {
            var msg = new ROMsg();
            msg.type = ROMsg.MsgType.Header;
            msg.name = k;
            msg.value = header.get(k);
            list.add(msg);
        }
        for (var f : ro.getClass().getFields()) {
            if (Modifier.isPublic(f.getModifiers()) && !Modifier.isStatic(f.getModifiers())) {
                try {
                    var v = f.get(ro);
                    if (v != null) {
                        var msg = new ROMsg();
                        msg.type = ROMsg.MsgType.Param;
                        msg.name = f.getName();
                        msg.value = v instanceof String ? (String) v : JsonUtil.toJson(v);
                        list.add(msg);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        var http = new FxHttp();
        var url = host + prefix + roname;
        var rs = http.post(url, JsonUtil.toJson(list));
        var rslist = JsonUtil.toList(rs, ROMsg.class);
        for (var m : rslist) {
            if (m.type.equals(ROMsg.MsgType.Error)) {
                throw new RemoteException(m.value);
            }
            if (!m.type.equals(ROMsg.MsgType.Result)) {
                continue;
            }
            try {
                var f = ro.getClass().getField(m.name);
                f.trySetAccessible();
                if (ro instanceof RO set) {
                    var v = set.setField(m.name, m.value);
                    if (v != void.class) {
                        f.set(ro, v);
                        continue;
                    }
                }
                if (m.value.startsWith("[")) {
                    if (f.getType().isArray()) {
                        f.set(ro, JsonUtil.toArray(m.value, f.getType()));
                    } else {
                        f.set(ro, JsonUtil.toList(m.value, f.getType()));
                    }
                } else if (f.getType().getCanonicalName().startsWith("java.")
                        && !f.getType().isArray()
                        && !(f.getType().isAssignableFrom(List.class))
                        && !(f.getType().isAssignableFrom(Map.class))) {
                    f.set(ro, ConvertUtil.change(m.value, f.getType()));
                } else {
                    f.set(ro, JsonUtil.toObject(m.value, f.getType()));
                }
            } catch (Exception e) {
                if (ro instanceof RO set) {
                    set.setOnFail(m.name, m.value);
                } else {
                    e.printStackTrace();
                }
            }
        }
    }

    protected String getRemoteHost(String name, Class<?> type) {
        for (var p : remoteProviders) {
            var rs = p.get(name, type);
            if (rs != null) {
                return rs;
            }
        }
        return null;
    }

    private static void exec(ROHandle handle, ProcessContext context) {
        var before = new ProcessBeforeEventArgs(context);
        evBefore.fire(bkey, handle, before);
        if (!before.isProcessed) {
            try {
                handle.process(context);
            } catch (Exception ex) {
                var error = new ProcessErrorEventArgs(context, ex);
                evError.fire(ekey, handle, error);
                if (error.isThrow) {
                    throw ex;
                }
            }
        }
        var after = new ProcessAfterEventArgs(context);
        evAfter.fire(akey, handle, after);
    }

    public static void start(FxSpringApp app) {
        app.getMvcEvents().evRequestBefore.add(ROContext::onRequest);
    }

    private static void onRequest(Object sender, RequestEventArgs args) {
        if (!args.request.getRequestURI().startsWith(prefix)) {
            return;
        }
        var ctx = contextFromHttp(args.request, ((FxSpringApp) sender).getApplicationContext(args.request));
        try {
            var pctx = new ProcessContext(ctx.ro);
            pctx.header.putAll(ctx.header);
            exec(ctx.handle, pctx);
        } catch (Exception ex) {
            ctx.error = ex;
        }
        var msglist = new ArrayList<ROMsg>();
        if (ctx.error == null) {
            for (var f : ctx.meta.roType.getFields()) {
                if (!ctx.params.contains(f.getName())) {
                    try {
                        var value = f.get(ctx.ro);
                        if (value != null) {
                            var msg = new ROMsg();
                            msg.type = ROMsg.MsgType.Result;
                            msg.name = f.getName();
                            if (value.getClass().getTypeName().startsWith("java.")
                                    && !value.getClass().isArray()
                                    && !(value instanceof List<?>)
                                    && !(value instanceof Map<?, ?>)) {
                                msg.value = String.valueOf(value);
                            } else {
                                msg.value = JsonUtil.toJson(value);
                            }
                            msglist.add(msg);
                        }
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        } else {
            var msg = new ROMsg();
            msg.type = ROMsg.MsgType.Error;
            msg.name = "msg";
            msg.value = ctx.error.getMessage();
            msglist.add(msg);
        }
        try {
            args.response.setCharacterEncoding("UTF-8");
            args.response.setContentType("application/json");
            args.response.getOutputStream().write(JsonUtil.toJson(msglist).getBytes(StandardCharsets.UTF_8));
            args.response.getOutputStream().close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static CallContext contextFromHttp(HttpServletRequest request, ApplicationContext context) {
        var ctx = new CallContext();
        ctx.container = context;
        ctx.name = request.getRequestURI().substring(prefix.length());
        ctx.meta = findMeta(ctx.name);
        FxAssert.isNotNull(ctx.meta, "没有找到" + ctx.name + "的处理类型");
        ctx.handle = ctx.container.getBean(ctx.meta.handleType);
        ctx.ro = ClassUtil.setNew(ctx.meta.roType);
        var params = new HashMap<String, String>();
        String kvs = "";
        if ("post".equalsIgnoreCase(request.getMethod())) {
            try {
                var data = StringReader.readToEnd(request.getInputStream());
                data = UrlUtil.decode(data).replaceAll("\\n", "&");
                if (StringUtil.hasValue(data)) {
                    if (request.getContentType().contains("json") && data.startsWith("[")) {
                        FxStream.create(JsonUtil.toList(data, ROMsg.class)).foreach(i -> {
                            if (i.type.equals(ROMsg.MsgType.Param)) {
                                params.put(i.name, i.value);
                            } else if (i.type.equals(ROMsg.MsgType.Header)) {
                                ctx.header.put(i.name, ConvertUtil.change(i.value, String.class));
                            }
                        });
                    } else if (request.getContentType().contains("x-www-form-urlencoded")) {
                        kvs = data;
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        if (StringUtil.hasValue(request.getQueryString())) {
            kvs += "&" + request.getQueryString();
        }
        if (StringUtil.hasValue(kvs)) {
            for (var i : kvs.split("&")) {
                var kv = i.split("=");
                if (kv.length == 2) {
                    params.put(kv[0], kv[1]);
                }
            }
        }
        for (var name : params.keySet()) {
            try {
                var f = ctx.meta.roType.getField(name);
                if (Modifier.isPublic(f.getModifiers()) && !Modifier.isStatic(f.getModifiers()) && f.trySetAccessible()) {
                    if (f.get(ctx.ro) != null) {
                        ctx.params.add(name);
                    } else {
                        var jv = params.get(name);
                        try {
                            var v = ctx.handle.setField(name, jv);
                            if (v != void.class) {
                                f.set(ctx.ro, v);
                            } else if (jv.startsWith("[")) {
                                if (f.getType().isArray()) {
                                    f.set(ctx.ro, JsonUtil.toArray(jv, f.getType()));
                                } else {
                                    f.set(ctx.ro, JsonUtil.toList(jv, f.getType()));
                                }
                            } else if (f.getType().getCanonicalName().startsWith("java.")) {
                                f.set(ctx.ro, ConvertUtil.change(params.get(name), f.getType()));
                            } else {
                                f.set(ctx.ro, JsonUtil.toObject(jv, f.getType()));
                            }
                        } catch (Exception e) {
                            ctx.handle.setOnFail(name, jv);
                        }
                        ctx.params.add(name);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ctx;
    }

    private static ROMeta findMeta(Class<?> type) {
        if (type == Object.class) {
            return null;
        }
        var list = type.getAnnotationsByType(ROName.class);
        if (list.length == 0 || list[0].value().isEmpty()) {
            var rs = metaMap.getOrDefault(type.getCanonicalName(), null);
            if (rs != null) {
                return rs;
            }
            return findMeta(type.getSuperclass());
        }
        return metaMap.getOrDefault(list[0].value(), null);
    }

    private static ROMeta findMeta(String ro) {
        var meta = metaMap.getOrDefault(ro, null);
        if (meta == null) {
            Class<?> type = ClassUtil.forName(ro);
            if (type != null) {
                type = type.getSuperclass();
                while (type != Object.class) {
                    meta = metaMap.getOrDefault(type.getCanonicalName(), null);
                    if (meta == null) {
                        type = type.getSuperclass();
                    } else {
                        break;
                    }
                }
            }
        }
        return meta;
    }

    public static <T extends ROHandle> void addHandler(Class<T> type) {
        ROMeta meta = new ROMeta();
        meta.handleType = type;
        var methods = FxStream.create(type.getDeclaredMethods());
        var find = methods.filter(i -> i.getName().equals("process")
                && i.getParameterCount() == 1
                && i.getParameterTypes()[0] == ProcessContext.class
                && Modifier.isPublic(i.getModifiers()));
        FxAssert.isEquals(find.count(), 1, "没有在" + type.getTypeName() + "类型中找到合适的处理方法");
        for (var fulname : type.getGenericInterfaces()) {
            if (fulname.getTypeName().startsWith(ROHandle.class.getCanonicalName() + "<")) {
                var roname = fulname.getTypeName().substring(ROHandle.class.getCanonicalName().length() + 1);
                roname = roname.substring(0, roname.lastIndexOf(">"));
                meta.roType = ClassUtil.forName(roname);
                break;
            }
        }
        FxAssert.isNotNull(meta.roType, "解析RO类型失败");
        meta.roName = getROName(meta.roType);
        metaMap.put(meta.roName, meta);
        if (!meta.roType.getTypeName().equals(meta.roName)) {
            metaMap.put(meta.roType.getTypeName(), meta);
        }
        for (var hp : remoteProviders) {
            hp.register(meta.roName, meta.roType);
        }
    }

    private static String getROName(Class<?> type) {
        var list = type.getAnnotationsByType(ROName.class);
        if (list.length == 1 && !list[0].value().isEmpty()) {
            return list[0].value();
        }
        while (type.getTypeName().contains("$") && type.getEnclosingMethod() != null) {
            type = type.getSuperclass();
        }
        return type.getTypeName();
    }

    public static void main(String[] args) {
        ROContext.addHandler(A.class);
    }

    private static class CallContext {
        public String name;
        public ROMeta meta;
        public ApplicationContext container;
        public Exception error;
        public Object ro;
        public ROHandle handle;
        public List<String> params = new ArrayList<>();
        public Map<String, String> header = new HashMap<>(8);
    }

    public interface HostProvider {
        String get(String name, Class<?> type);

        void register(String name, Class<?> type);
    }

    public static class Msg {
        public String p1;
        public Integer p2;
        public Date p3;
        public String r1;
        public Long r2;
        public List<Msg> r3;
    }

    public static class A implements ROHandle<ROContext> {
        @Override
        public void process(ProcessContext<ROContext> context) {

        }
    }
}
